package EA.operators.mutation;
import EA.*;
import RKUjava.util.*;
import RKUjava.math.*;

/** Normal Distributed mutation operators for discrete real numerical
    genomes. The mutation operators in this class are designed such
    that if a mutation occurs then the genome is changed on at least 
    one of the entries. I.e., if the the small change is rounded
    to zero in all cases, then a random entry is picked an the corresponding 
    stepsize is added/subtracted.
 */
public class DRNumExpMutation extends NumMutation
{

  /** Mutate all entries in a given genome. This
      mutation operator will only mutate a specific number of the entries in
      the vector represented by the numerical genome. If the genome
      contains 200 parameters and you ask this method to mutate 10% of
      the genome then 20 of the entries will be changed. The entries
      are picked at random and no checking is done to ensure that an
      entry is only mutated once (time consuming).
      @param genome The NumericalGenome to mutate.
      @param params Parameters for the mutation operator. params[0] =
      probability of mutation, params[1] = variance of the normal
      distribution, params[2] = scaling constant for interval (usually
      1/10).  */
  public static void mutateAll(NumericalGenome genome, double[] params)
    {
      int number;
      double adder;
      double oldposval;
      int i;
      double[] pos;
      Interval curinterval;
      DiscreteRNumGenome dgenome = (DiscreteRNumGenome)genome;
      boolean changed = false;

      if (RKURandom.randomBiasedBoolean(params[0])) {
	pos = dgenome.getPosReference();
	number = pos.length;
      
	for (i=0;i<number;i++) {
	  curinterval = dgenome.drnf.intervals[i];
	  adder = curinterval.getLength()*params[2]*RKURandom.getNormalDistributed(0,RKURandom.getExponentialDistributed(params[1]));

	  oldposval = pos[i];
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  if (Math.abs(adder)>dgenome.drnf.discrete_stepsize[i]/2 && oldposval!=pos[i]) {
	    changed = true;
	  }
	}
	if (!changed) {
	  i = RKURandom.randomInt(pos.length);
	  curinterval = ((NumericalFramework)dgenome.framework).intervals[i];
	  adder = RKURandom.randomSgn()*dgenome.drnf.discrete_stepsize[i];

	  oldposval = pos[i];
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  if (oldposval==pos[i]) {
	    // Upper/lower limit -> flip adder and try again.
	    adder = -1*adder;
	    pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  }
	}
	dgenome.setPosReference(pos);
	dgenome.fitnesscalculated = false;
      }
    }

  /** Mutate a fraction of a given genome. This mutation operator will
      only mutate a fraction of the entries in the vector represented
      by the numerical genome. If the genome contains 200 parameters and 
      you ask this method to mutate 10% of the genome then 20 of the entries
      will be changed. The entries are picked at random and no
      checking is done to ensure that an entry is only mutated once
      (time consuming).
      @param genome The NumericalGenome to mutate.
      @param params Parameters for the mutation operator. params[0] =
      probability of mutation, params[1] = variance of the normal
      distribution, params[2] = scaling constant for interval (usually
      1/10), params[3] = fraction of entries to mutate (value between 0 and 1).*/
  public static void mutateFraction(NumericalGenome genome, double[] params)
    {
      int frac;
      double adder;
      int i,cnt;
      double[] pos;
      Interval curinterval;
      double oldposval;
      DiscreteRNumGenome dgenome = (DiscreteRNumGenome)genome;
      boolean changed = false;

      if (RKURandom.randomBiasedBoolean(params[0])) {
	pos = dgenome.getPosReference();
	frac = Math.max(1,(int)(params[3]*pos.length));
      
	for (cnt=0;cnt<frac;cnt++) {
	  i = RKURandom.randomInt(pos.length);
	  curinterval = dgenome.drnf.intervals[i];
	  adder = curinterval.getLength()*params[2]*RKURandom.getNormalDistributed(0,RKURandom.getExponentialDistributed(params[1]));

	  oldposval = pos[i];
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  if (Math.abs(adder)>dgenome.drnf.discrete_stepsize[i]/2 && oldposval!=pos[i]) {
	    changed = true;
	  }
  
	}
	if (!changed) {
	  i = RKURandom.randomInt(pos.length);
	  curinterval = ((NumericalFramework)dgenome.framework).intervals[i];
	  adder = RKURandom.randomSgn()*dgenome.drnf.discrete_stepsize[i];
	
	  oldposval = pos[i];
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  if (oldposval==pos[i]) {
	    // Upper/lower limit -> flip adder and try again.
	    adder = -1*adder;
	    pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  }
	}
	dgenome.setPosReference(pos);
	dgenome.fitnesscalculated = false;
      }
    }

  /** Mutate a fixed number of entries in a given genome. This
      mutation operator will only mutate a specific number of the
      entries in the vector represented by the numerical genome. If
      the genome contains 200 parameters and you ask this method to
      mutate 10% of the genome then 20 of the entries will be
      changed. The entries are picked at random and no checking is
      done to ensure that an entry is only mutated once (time
      consuming).
      @param genome The NumericalGenome to mutate.
      @param params Parameters for the mutation operator. params[0] =
      probability of mutation, params[1] = variance of the normal
      distribution, params[2] = scaling constant for interval (usually
      1/10), params[3] = number of entries to mutate. */
  public static void mutateFixedNumber(NumericalGenome genome, double[] params)
    {
      int number;
      double adder;
      int i, cnt;
      double[] pos;
      Interval curinterval;
      double oldposval;
      DiscreteRNumGenome dgenome = (DiscreteRNumGenome)genome;
      boolean changed = false;

      if (RKURandom.randomBiasedBoolean(params[0])) {
	pos = dgenome.getPosReference();
	number = Math.max(1,(int)params[3]);
      
	for (cnt=0;cnt<number;cnt++) {
	  i = RKURandom.randomInt(pos.length);
	  curinterval = dgenome.drnf.intervals[i];
	  adder = curinterval.getLength()*params[2]*RKURandom.getNormalDistributed(0,RKURandom.getExponentialDistributed(params[1]));

	  oldposval = pos[i];
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  if (Math.abs(adder)>dgenome.drnf.discrete_stepsize[i]/2 && oldposval!=pos[i]) {
	    changed = true;
	  }
	}

	if (!changed) {
	  i = RKURandom.randomInt(pos.length);
	  curinterval = ((NumericalFramework)dgenome.framework).intervals[i];
	  adder = RKURandom.randomSgn()*dgenome.drnf.discrete_stepsize[i];
	  
	  oldposval = pos[i];
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  if (oldposval==pos[i]) {
	    // Upper/lower limit -> flip adder and try again.
	    adder = -1*adder;
	    pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  }
	}

	dgenome.setPosReference(pos);
	dgenome.fitnesscalculated = false;
      }
    }

  /** Mutate entries in the given vector such that each entry has a
      low probability of being mutated. This mutation operator will
      traverse the entries in the genome, and with the given
      probability mutate the entry.
      @param genome The NumericalGenome to mutate.
      @param params Parameters for the mutation operator. 
      params[0] = probability of mutating an entry, params[1] = variance of the normal
      distribution, params[2] = scaling constant for interval (usually
      1/10).  */
  public static void mutateRollEach(NumericalGenome genome, double[] params)
    {
      double adder;
      int i;
      double[] pos;
      Interval curinterval;
      double oldposval;
      DiscreteRNumGenome dgenome = (DiscreteRNumGenome)genome;
      boolean changed = false;

      pos = dgenome.getPosReference();
      
      for (i=0;i<pos.length;i++) {
	if (RKURandom.randomBiasedBoolean(params[0])) {
	  curinterval = dgenome.drnf.intervals[i];
	  adder = curinterval.getLength()*params[2]*RKURandom.getNormalDistributed(0,RKURandom.getExponentialDistributed(params[1]));

	  oldposval = pos[i];
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	  if (Math.abs(adder)>dgenome.drnf.discrete_stepsize[i]/2 && oldposval!=pos[i]) {
	    changed = true;
	  }
	}
      }

      if (!changed) {
	i = RKURandom.randomInt(pos.length);
	curinterval = ((NumericalFramework)dgenome.framework).intervals[i];
	adder = RKURandom.randomSgn()*dgenome.drnf.discrete_stepsize[i];
	
	oldposval = pos[i];
	pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	if (oldposval==pos[i]) {
	  // Upper/lower limit -> flip adder and try again.
	  adder = -1*adder;
	  pos[i] = curinterval.bringInClosedRange(pos[i]+adder);
	}
      }	
      dgenome.setPosReference(pos);
      dgenome.fitnesscalculated = false;
    }

}
